package org.ithasu.tools.bugOne;

import java.util.ArrayList;

import org.ithasu.tools.Console;
import org.ithasu.tools.IOTools;
import org.ithasu.tools.bugOne.SerialSniffer.MessageListener;

public class BugOneSniffer implements MessageListener {

	public final static byte PACKET_HELLO = 0x01;
	public final static byte PACKET_PING = 0x02;
	public final static byte PACKET_PONG = 0x03;
	public final static byte PACKET_GET = 0x04;
	public final static byte PACKET_SET = 0x05;
	public final static byte PACKET_VALUES = 0x06;

	private final SerialSniffer sniffer;
	private final byte nodeId;
	private int counter = 0;

	private boolean transferAllMessages = false;

	private final ArrayList<MessageListener> messageListeners = new ArrayList<MessageListener>();
	private final ArrayList<BugOneListener> inputListeners = new ArrayList<BugOneListener>();

	public BugOneSniffer(SerialSniffer sniffer, byte nodeId) {
		this.sniffer = sniffer;
		this.nodeId = nodeId;

		if (this.sniffer == null) {
			Console.error(new NullPointerException());
			return;
		}

		// Register to listener
		this.sniffer.addMessageListener(this);
	}

	// -- Properties ----------------------------------------

	/**
	 * If true, will fire callback on all MessageListeners if message do not
	 * concern sniffer node id.
	 */
	public boolean isTransferAllMessages() {
		return transferAllMessages;
	}

	public void setTransferAllMessages(boolean transferAllMessages) {
		this.transferAllMessages = transferAllMessages;
	}

	// -- Messages ----------------------------------------

	public boolean ping(byte destNodeId) {
		// build packet
		byte packet[] = packetNew();
		int index = packetPrepare(packet, destNodeId, PACKET_PING, 0);
		index = packetFill(packet, index);
		// send
		return sniffer.send(packet);
	}

	public boolean pong(byte destNodeId) {
		// build packet
		byte packet[] = packetNew();
		int index = packetPrepare(packet, destNodeId, PACKET_PONG, 0);
		index = packetFill(packet, index);
		// send
		return sniffer.send(packet);
	}

	// -- Values ----------------------------------------

	public boolean set(byte destNodeId, byte srcDeviceId, byte destDeviceId,
			int value) {
		// build packet
		byte packet[] = packetNew();
		int index = packetPrepare(packet, destNodeId, PACKET_SET, 0);
		index = packetAdd(packet, srcDeviceId, index);
		index = packetAdd(packet, destDeviceId, index);
		index = packetAdd(packet, (byte) 'I', index);
		index = packetAddInt(packet, value, index);
		index = packetFill(packet, index);
		// send
		return sniffer.send(packet);
	}

	// -- Packet ----------------------------------------

	private byte[] packetNew() {
		return new byte[32];
	}

	private int packetAdd(byte[] packet, byte c, int index) {
		if (index < 0 || index >= packet.length) {
			Console.error(new IllegalStateException());
			return -1;
		}

		packet[index++] = c;

		return index;
	}

	private int packetAddInt(byte[] packet, int i, int index) {
		index = packetAdd(packet, (byte) ((i & 0x0FF00) >> 8), index);
		index = packetAdd(packet, (byte) ((i & 0x0FF)), index);
		return index;
	}

	private int packetFill(byte[] packet, int index) {
		while (index >= 0 && index < packet.length) {
			packet[index++] = 0x00;
		}
		return index;
	}

	private int packetPrepare(byte[] packet, byte destNodeId, byte packetType,
			int index) {
		index = packetAdd(packet, this.nodeId, index);
		index = packetAdd(packet, destNodeId, index);
		index = packetAdd(packet, (byte) 0x00, index); // Not handled by now
		index = packetAdd(packet, packetType, index);
		index = packetAddInt(packet, ++counter, index);
		return index;
	}

	// -- Listeners ----------------------------------------

	/**
	 * Message listeners will be called only if message cannot be handled by
	 * BugOneSniffer.
	 */
	public void addMessageListener(MessageListener listener) {
		messageListeners.add(listener);
	}

	public void addBugOneListener(BugOneListener listener) {
		inputListeners.add(listener);
	}

	// -- Events ----------------------------------------

	protected void fireMessageEventIfNeeded(SerialSniffer sniffer,
			byte[] message) {
		if (transferAllMessages) {
			for (MessageListener l : messageListeners) {
				l.onMessageReceived(sniffer, message);
			}
		}
	}

	protected void firePongEvent(byte srcNodeId) {
		for (BugOneListener l : inputListeners) {
			l.onPongEvent(srcNodeId);
		}
	}

	protected void fireInputEvent(byte srcNodeId, byte srcDeviceId,
			byte destDeviceId, Object value) {
		for (BugOneListener l : inputListeners) {
			l.onInputEvent(srcNodeId, srcDeviceId, destDeviceId, value);
		}
	}

	// -- Message handling ----------------------------------------

	@Override
	public void onMessageReceived(SerialSniffer sniffer, byte[] message) {
		try {
			byte srcNodeId = message[0];
			byte destNodeId = message[1];
			// char routerId = message[2]; // Don't care by now
			byte packetType = message[3];
			if (destNodeId != (byte) 0xff && destNodeId != nodeId) {
				// Not concerned by message
				fireMessageEventIfNeeded(sniffer, message);
				return;
			}

			counter = IOTools.readInt(message[4], message[5]);

			Console.debug("counter=" + counter);

			switch (packetType) {
			case PACKET_HELLO:
				Console.debug("Node " + Integer.toHexString(srcNodeId)
						+ " says Hello");
				return;
			case PACKET_PING:
				pong(srcNodeId);
				return;
			case PACKET_PONG:
				firePongEvent(srcNodeId);
				return;
			case PACKET_VALUES:
				int index = 6;
				boolean needToFire = false;
				while (index + 3 < message.length) {
					byte srcDeviceId = message[index++];
					byte destDeviceId = message[index++];
					byte valueType = message[index++];
					Object value = null;
					switch (valueType) {
					case 'I':
						value = IOTools.readInt(message[index++],
								message[index++]);
						break;
					case 'S':
						byte text[] = new byte[message[index++]];
						for (int i = 0; i < text.length; i++) {
							text[i] = message[index++];
						}
						value = new String(text);
						break;
					default:
						// A value cannot be red
						needToFire = true;
					}
					if (value != null) {
						fireInputEvent(srcNodeId, srcDeviceId, destDeviceId,
								value);
					} else {
						needToFire = true;
					}
				}

				if (needToFire) {
					fireMessageEventIfNeeded(sniffer, message);
				}
				return;
			}
		} catch (Exception e) {
			// Don't care, just fire message
		}

		fireMessageEventIfNeeded(sniffer, message);
	}

	// -- Internal classes ----------------------------------------

	public static interface BugOneListener {
		public void onPongEvent(byte srcNodeId);

		public void onInputEvent(byte srcNodeId, byte srcDeviceId,
				byte destDeviceId, Object value);
	}

	public static class BugOneAdapter implements BugOneListener {
		@Override
		public void onPongEvent(byte srcNodeId) {
		}

		@Override
		public void onInputEvent(byte srcNodeId, byte srcDeviceId,
				byte destDeviceId, Object value) {
		}
	}
}
